from flask import Flask, request, jsonify, Response, stream_with_context, session
from flask_cors import CORS
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
import os
import redis
import json
import datetime
import pymysql
from dotenv import load_dotenv
import requests
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models import ChatOpenAI
from langchain_community.llms import Tongyi
from langchain_community.vectorstores.redis import Redis as RedisVectorStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.chains.question_answering import load_qa_chain
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.embeddings import FakeEmbeddings
from langchain.vectorstores.redis import RedisText
import time
from openai import OpenAI
from dashscope import MultiModalConversation
import dashscope
import tempfile
import base64
import copy

# Load environment variables
load_dotenv()

# 初始化 OpenAI 客户端
def init_openai_client():
    try:
        # 获取API密钥
        dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
        if not dashscope_api_key:
            # 设置默认API Key
            default_api_key = "sk-0fcfde37071d44058afad63fa810c5e3"
            print(f"设置默认API Key作为DASHSCOPE_API_KEY")
            os.environ["DASHSCOPE_API_KEY"] = default_api_key
            dashscope_api_key = default_api_key
        
        # 初始化客户端
        client = OpenAI(
            api_key=dashscope_api_key,
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            timeout=60.0  # 增加超时时间
        )
        print(f"OpenAI客户端初始化成功，API密钥: {dashscope_api_key[:8]}...")
        return client
    except Exception as e:
        print(f"OpenAI客户端初始化失败: {str(e)}")
        raise

# 全局变量
openai_client = init_openai_client()

# 检查并设置DASHSCOPE_API_KEY
def check_and_set_dashscope_api_key():
    # 设置默认API Key
    default_api_key = "sk-0fcfde37071d44058afad63fa810c5e3"
    
    # 检查是否需要使用代理
    use_proxy = os.getenv("USE_PROXY", "false").lower() == "true"
    if use_proxy:
        proxy = os.getenv("HTTP_PROXY", "http://127.0.0.1:7890")
        print(f"已启用代理: {proxy}")
        os.environ["http_proxy"] = proxy
        os.environ["https_proxy"] = proxy
    
    # 检查是否提供了自定义API密钥
    dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
    if not dashscope_api_key:
        # 使用默认API Key
        print(f"使用默认API Key作为DASHSCOPE_API_KEY")
        os.environ["DASHSCOPE_API_KEY"] = default_api_key
        
        # 尝试从OPENAI_API_KEY获取（如果有的话）
        openai_api_key = os.getenv("OPENAI_API_KEY")
        if openai_api_key and openai_api_key.startswith("sk-"):
            print("使用OPENAI_API_KEY作为DASHSCOPE_API_KEY")
            os.environ["DASHSCOPE_API_KEY"] = openai_api_key
    
    # 确保API密钥在环境变量中可用
    print(f"DASHSCOPE_API_KEY已设置: {os.environ['DASHSCOPE_API_KEY'][:8]}...")

# 检查API密钥
check_and_set_dashscope_api_key()

app = Flask(__name__)
CORS(app)

# Configure JWT
app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY", "super-secret")
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = datetime.timedelta(hours=24)
jwt = JWTManager(app)

# Database connection
def get_db_connection():
    max_retries = 3
    retry_delay = 2  # seconds
    
    for attempt in range(max_retries):
        try:
            print(f"尝试连接MySQL（第{attempt + 1}次）...")
            print(f"主机: {os.getenv('DB_HOST', 'localhost')}")
            print(f"端口: {os.getenv('DB_PORT', '3307')}")
            print(f"用户: {os.getenv('DB_USER', 'root')}")
            print(f"数据库: {os.getenv('DB_NAME', 'chat_app')}")
            
            conn = pymysql.connect(
                host=os.getenv("DB_HOST", "localhost"),
                port=int(os.getenv("DB_PORT", "3307")),
                user=os.getenv("DB_USER", "root"),
                password=os.getenv("DB_PASSWORD", "123456"),
                database=os.getenv("DB_NAME", "chat_app"),
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor
            )
            print("✅ MySQL连接成功！")
            return conn
            
        except pymysql.Error as e:
            print(f"❌ MySQL连接失败（第{attempt + 1}次）：{str(e)}")
            if attempt < max_retries - 1:
                print(f"等待{retry_delay}秒后重试...")
                time.sleep(retry_delay)
            else:
                print("\n请检查：")
                print("1. MySQL服务是否在运行")
                print("2. 端口3307是否正确（默认应该是3306）")
                print("3. 用户名和密码是否正确")
                print("4. 数据库是否已创建")
                raise

# Redis connection
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
try:
    redis_client.ping()
    print('Redis 连接成功 (localhost:6379)')
except Exception as e:
    print(f'Redis 连接失败: {e}')
    print('尝试创建内存模式的Redis客户端')
    try:
        # 如果本地Redis连接失败，使用FakeRedis作为备选方案
        from fakeredis import FakeRedis
        redis_client = FakeRedis()
        print('使用内存模式的FakeRedis作为备选方案')
    except ImportError:
        print('无法导入fakeredis，请安装: pip install fakeredis')
        # 如果fakeredis不可用，使用简单的内存字典模拟Redis
        class DictRedis:
            def __init__(self):
                self.data = {}
                self.expiry = {}
                print("使用内存字典模拟Redis功能")
            
            def get(self, key):
                return self.data.get(key)
            
            def set(self, key, value, ex=None):
                self.data[key] = value
                if ex:
                    self.expiry[key] = time.time() + ex
            
            def exists(self, key):
                if key in self.expiry and time.time() > self.expiry[key]:
                    del self.data[key]
                    del self.expiry[key]
                    return False
                return key in self.data
            
            def delete(self, key):
                if key in self.data:
                    del self.data[key]
                if key in self.expiry:
                    del self.expiry[key]
            
            def keys(self, pattern):
                import fnmatch
                result = []
                for key in self.data.keys():
                    if fnmatch.fnmatch(key, pattern):
                        # 检查是否过期
                        if key in self.expiry and time.time() > self.expiry[key]:
                            del self.data[key]
                            del self.expiry[key]
                        else:
                            result.append(key.encode())
                return result
            
            def expire(self, key, seconds):
                if key in self.data:
                    self.expiry[key] = time.time() + seconds
        
        redis_client = DictRedis()

# Initialize embeddings
enable_embeddings = os.getenv("ENABLE_EMBEDDINGS", "false").lower() == "true"
use_fake_embeddings = os.getenv("USE_FAKE_EMBEDDINGS", "true").lower() == "true"
embedding_model = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")

if enable_embeddings and not use_fake_embeddings:
    try:
        print("尝试加载Hugging Face嵌入模型...")
        embeddings = HuggingFaceEmbeddings(model_name=embedding_model)
        print(f"成功加载嵌入模型: {embedding_model}")
    except Exception as e:
        print(f"加载Hugging Face模型失败: {e}")
        print("自动切换到FakeEmbeddings...")
        embeddings = FakeEmbeddings(size=384)
        print("已切换到FakeEmbeddings（功能有限，仅供测试）")
else:
    print("根据配置使用FakeEmbeddings...")
    embeddings = FakeEmbeddings(size=384)
    print("已使用FakeEmbeddings（知识库功能将有限制）")

# Initialize LLM models
def get_llm(model_name="douban", streaming=False):
    if model_name == "douban":
        return ChatOpenAI(
            api_key=os.getenv("DOUBAN_API_KEY"),
            base_url=os.getenv("DOUBAN_API_BASE"),
            model_name=os.getenv("DOUBAN_MODEL_NAME", "douban-model"),
            temperature=0.7,
            streaming=streaming
        )
    elif model_name == "qianwen":
        # 检查是否使用Qwen-Omni-Turbo模型
        qwen_model_name = os.getenv("QIANWEN_MODEL_NAME", "qwen-plus")
        if qwen_model_name == "qwen-omni-turbo":
            # 确保API Key已设置
            dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
            if not dashscope_api_key:
                # 设置默认API Key
                default_api_key = "sk-0fcfde37071d44058afad63fa810c5e3"
                print(f"设置默认API Key作为DASHSCOPE_API_KEY")
                os.environ["DASHSCOPE_API_KEY"] = default_api_key
                dashscope_api_key = default_api_key
            # 使用OpenAI客户端适配器
            return ChatOpenAI(
                api_key=os.getenv("DASHSCOPE_API_KEY"),
                base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                model_name="qwen-omni-turbo",
                temperature=0.7,
                streaming=streaming,
                openai_api_key=os.getenv("DASHSCOPE_API_KEY")  # 明确设置openai_api_key参数
            )
        else:
            # 使用原始Tongyi适配器
            return Tongyi(
                model_name=qwen_model_name,
                api_key=os.getenv("QIANWEN_API_KEY"),
                temperature=0.7,
                streaming=streaming
            )
    else:
        # Default to douban
        return ChatOpenAI(
            api_key=os.getenv("DOUBAN_API_KEY"),
            base_url=os.getenv("DOUBAN_API_BASE"),
            model_name=os.getenv("DOUBAN_MODEL_NAME", "douban-model"),
            temperature=0.7,
            streaming=streaming
        )

# Create tables if they don't exist
def init_db():
    print("\n开始初始化数据库...")
    try:
        # 首先尝试创建数据库（如果不存在）
        root_conn = pymysql.connect(
            host=os.getenv("DB_HOST", "localhost"),
            port=int(os.getenv("DB_PORT", "3307")),
            user=os.getenv("DB_USER", "root"),
            password=os.getenv("DB_PASSWORD", "123456"),
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        with root_conn.cursor() as cursor:
            cursor.execute(f"CREATE DATABASE IF NOT EXISTS {os.getenv('DB_NAME', 'chat_app')}")
        root_conn.close()
        print("✅ 数据库创建成功")
        
        # 然后连接到特定数据库并创建表
        conn = get_db_connection()
        try:
            with conn.cursor() as cursor:
                # Users table
                cursor.execute("""
                CREATE TABLE IF NOT EXISTS users (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    username VARCHAR(100) UNIQUE NOT NULL,
                    password VARCHAR(255) NOT NULL,
                    email VARCHAR(255) UNIQUE,
                    role ENUM('user', 'admin') DEFAULT 'user',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
                """)
                print("✅ users表创建成功")
                
                # Tasks table
                cursor.execute("""
                CREATE TABLE IF NOT EXISTS tasks (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    title VARCHAR(255) NOT NULL,
                    description TEXT,
                    due_date DATETIME,
                    status VARCHAR(50) DEFAULT 'pending',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (user_id) REFERENCES users(id)
                )
                """)
                print("✅ tasks表创建成功")
                
                # Knowledge bases table
                cursor.execute("""
                CREATE TABLE IF NOT EXISTS knowledge_bases (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    name VARCHAR(255) NOT NULL,
                    description TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (user_id) REFERENCES users(id)
                )
                """)
                print("✅ knowledge_bases表创建成功")
                
                conn.commit()
                print("\n✅ 数据库初始化完成！")
                
        finally:
            conn.close()
            
    except Exception as e:
        print(f"\n❌ 数据库初始化失败：{str(e)}")
        raise

# Initialize database
init_db()

# Authentication routes
@app.route('/api/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    email = data.get('email', '')
    role = data.get('role', 'user')  # 默认为普通用户
    
    if not username or not password:
        return jsonify({"error": "Username and password are required"}), 400
    
    if role not in ['user', 'admin']:
        return jsonify({"error": "Invalid role"}), 400
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            # Check if username already exists
            cursor.execute("SELECT id FROM users WHERE username = %s", (username,))
            if cursor.fetchone():
                return jsonify({"error": "Username already exists"}), 409
            
            # Insert new user
            cursor.execute(
                "INSERT INTO users (username, password, email, role) VALUES (%s, %s, %s, %s)",
                (username, password, email, role)
            )
            conn.commit()
            
            # Create access token
            access_token = create_access_token(identity=username)
            return jsonify({
                "message": "User registered successfully",
                "access_token": access_token,
                "role": role
            }), 201
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if not username or not password:
        return jsonify({"error": "Username and password are required"}), 400
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute(
                "SELECT id, username, role FROM users WHERE username = %s AND password = %s",
                (username, password)
            )
            user = cursor.fetchone()
            
            if user:
                access_token = create_access_token(identity=username)
                return jsonify({
                    "message": "Login successful",
                    "access_token": access_token,
                    "role": user['role']
                }), 200
            else:
                return jsonify({"error": "Invalid username or password"}), 401
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

# Chat routes
@app.route('/api/chat', methods=['POST'])
@jwt_required()
def chat():
    current_user = get_jwt_identity()
    
    # 检查是否是JSON请求还是表单请求
    if request.is_json:
        data = request.get_json()
        message = data.get('message')
        conversation_id = data.get('conversation_id')
        model_name = data.get('model', 'douban')
        stream = data.get('stream', False)
        
        # 处理文件上传
        file_data = data.get('file_data')
        page_type = data.get('page_type', 'chat')  # 获取当前页面类型
        
        # 如果是知识问答页面，只允许上传文档文件
        if page_type == 'knowledge' and file_data:
            file_type = file_data.get('type')
            allowed_types = ['application/pdf', 'text/plain', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
            
            if file_type not in allowed_types:
                return jsonify({"error": "Only PDF, TXT, and DOCX files are allowed in knowledge query"}), 400
            
            # 处理文档文件
            file_base64 = file_data.get('base64')
            file_name = file_data.get('name')
            
            # 检查message类型，适当处理
            if isinstance(message, dict):
                # 如果message是字典（通义千问格式），添加到content中
                if isinstance(message.get('content'), list):
                    message['content'].append({
                        "type": "text",
                        "text": f"\n[用户上传了文件: {file_name}]"
                    })
            else:
                # 普通字符串消息
                message += f"\n[用户上传了文件: {file_name}]"
        
        # 如果是普通聊天页面，允许所有类型的文件
        elif page_type == 'chat' and file_data:
            file_base64 = file_data.get('base64')
            file_type = file_data.get('type')
            file_name = file_data.get('name')
            
            # 检查message类型，适当处理
            if isinstance(message, dict):
                # 如果message是字典（通义千问格式）
                if isinstance(message.get('content'), list):
                    if file_type.startswith('image/'):
                        message['content'].append({
                            "type": "text",
                            "text": f"\n[用户上传了一张图片: {file_name}]"
                        })
                    elif file_type.startswith('audio/'):
                        message['content'].append({
                            "type": "text",
                            "text": f"\n[用户上传了一段音频: {file_name}]"
                        })
                    else:
                        message['content'].append({
                            "type": "text",
                            "text": f"\n[用户上传了文件: {file_name}]"
                        })
            else:
                # 普通字符串消息
                if file_type.startswith('image/'):
                    message += f"\n[用户上传了一张图片: {file_name}]"
                elif file_type.startswith('audio/'):
                    message += f"\n[用户上传了一段音频: {file_name}]"
                else:
                    message += f"\n[用户上传了文件: {file_name}]"
    else:
        # 表单请求处理
        message = request.form.get('message')
        conversation_id = request.form.get('conversation_id')
        model_name = request.form.get('model', 'douban')
        stream = request.form.get('stream', False) == 'true'
        page_type = request.form.get('page_type', 'chat')  # 获取当前页面类型
        
        # 处理上传的文件
        if 'file' in request.files:
            file = request.files['file']
            if file and file.filename:
                # 如果是知识问答页面，检查文件类型
                if page_type == 'knowledge':
                    allowed_extensions = {'pdf', 'txt', 'docx'}
                    file_extension = file.filename.split('.')[-1].lower() if '.' in file.filename else ''
                    
                    if file_extension not in allowed_extensions:
                        return jsonify({"error": "Only PDF, TXT, and DOCX files are allowed in knowledge query"}), 400
                
                import base64
                from io import BytesIO
                
                # 读取文件内容并转换为base64
                file_content = file.read()
                file_base64 = base64.b64encode(file_content).decode('utf-8')
                
                # 检查message类型，适当处理
                if isinstance(message, dict):
                    # 如果message是字典（通义千问格式）
                    if isinstance(message.get('content'), list):
                        if file.content_type.startswith('image/'):
                            message['content'].append({
                                "type": "text",
                                "text": f"\n[用户上传了一张图片: {file.filename}]"
                            })
                        elif file.content_type.startswith('audio/'):
                            message['content'].append({
                                "type": "text",
                                "text": f"\n[用户上传了一段音频: {file.filename}]"
                            })
                        else:
                            message['content'].append({
                                "type": "text",
                                "text": f"\n[用户上传了文件: {file.filename}]"
                            })
                else:
                    # 普通字符串消息
                    if file.content_type.startswith('image/'):
                        message += f"\n[用户上传了一张图片: {file.filename}]"
                    elif file.content_type.startswith('audio/'):
                        message += f"\n[用户上传了一段音频: {file.filename}]"
                    else:
                        message += f"\n[用户上传了文件: {file.filename}]"
    
    if not message:
        return jsonify({"error": "Message is required"}), 400
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    # 确保使用页面类型作为内存键的一部分，以区分不同功能页面的对话
    memory_key = f"chat:{user_id}:{page_type}:{conversation_id}"
    print(f"使用内存键: {memory_key}")
    
    # Check if we have existing memory
    if redis_client.exists(memory_key):
        # Retrieve existing conversation history
        conversation_history = json.loads(redis_client.get(memory_key))
        memory = ConversationBufferMemory(return_messages=True)
        
        # Rebuild memory from history
        for entry in conversation_history:
            if entry["type"] == "human":
                memory.chat_memory.add_user_message(entry["content"])
            else:
                memory.chat_memory.add_ai_message(entry["content"])
    else:
        # Create new memory
        memory = ConversationBufferMemory(return_messages=True)
    
    # Create LLM chain
    llm = get_llm(model_name)
    
            # 检查是否是Qwen-Omni-Turbo的特殊消息格式
    is_qwen_omni = model_name == "qianwen" and isinstance(message, dict) and message.get("role") == "user"
    print(f"模型: {model_name}, 是否使用Qwen-Omni格式: {is_qwen_omni}, 消息类型: {type(message)}")
    
    if is_qwen_omni:
        # 使用Qwen-Omni-Turbo的特殊处理
        try:
                         # 直接使用OpenAI客户端发送请求
            dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
            if not dashscope_api_key:
                # 设置默认API Key
                default_api_key = "sk-0fcfde37071d44058afad63fa810c5e3"
                print(f"设置默认API Key作为DASHSCOPE_API_KEY")
                os.environ["DASHSCOPE_API_KEY"] = default_api_key
                dashscope_api_key = default_api_key
                
            # 添加更多的连接参数和错误处理
            try:
                client = OpenAI(
                    api_key=dashscope_api_key,
                    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                    timeout=60.0,  # 增加超时时间
                )
                print(f"OpenAI客户端初始化成功，API密钥: {dashscope_api_key[:8]}...")
            except Exception as client_err:
                print(f"OpenAI客户端初始化失败: {str(client_err)}")
                raise
            
            # 构建请求
            print(f"\n准备发送请求到Qwen-Omni-Turbo模型")
            print(f"消息类型: {type(message)}")
            if isinstance(message, dict) and "content" in message:
                print(f"消息内容类型: {[item.get('type') for item in message['content'] if isinstance(item, dict)]}")
            
            # 检查消息内容类型
            content_types = []
            if isinstance(message, dict) and isinstance(message.get("content"), list):
                content_types = [item.get('type') for item in message.get("content", []) if isinstance(item, dict) and 'type' in item]
            
            # 根据内容类型设置不同的参数
            # 检查是否有音频内容，如果有则使用qwen-audio-turbo-latest模型
            has_audio = False
            if isinstance(message, dict) and isinstance(message.get("content"), list):
                for item in message.get("content", []):
                    if isinstance(item, dict) and (item.get("type") == "audio" or "audio" in item):
                        has_audio = True
                        break
            
            model_to_use = "qwen-audio-turbo-latest" if has_audio else "qwen-omni-turbo"
            print(f"选择的模型: {model_to_use}")
            
            # 准备请求参数
            messages = [message]
            
            # 添加系统消息
            if model_to_use == "qwen-audio-turbo-latest":
                # 为音频模型添加系统消息
                system_message = {
                    "role": "system", 
                    "content": [{"type": "text", "text": "你是通义千问的语音助手，可以处理音频内容并提供专业回答。"}]
                }
                messages.insert(0, system_message)
            
            completion_args = {
                "model": model_to_use,
                "messages": messages,
                "stream": stream,
                "temperature": 0.7,
                "max_tokens": 2000
            }
            
            # 如果包含音频，添加音频输出模态
            if "audio" in content_types:
                print("检测到音频内容，不添加音频输出模态以避免错误")
            
            # 如果是纯文本消息，可以添加音频输出
            if len(content_types) == 0 or (len(content_types) == 1 and "text" in content_types):
                print("纯文本消息，可以添加音频输出模态")
                # completion_args["modalities"] = ["text", "audio"]
                # completion_args["audio"] = {"voice": "Cherry", "format": "wav"}
            
            print(f"请求参数: {completion_args}")
            
            if stream:
                # 处理流式响应
                def generate_qwen_omni():
                    full_response = []
                    
                    try:
                        print("开始发送请求到Qwen-Omni-Turbo...")
                        try:
                            print(f"正在发送请求到模型: {model_to_use}，API密钥: {dashscope_api_key[:8]}...")
                            
                            # 打印详细的请求信息，帮助调试
                            print("请求参数详情:")
                            for i, msg in enumerate(completion_args["messages"]):
                                print(f"消息 {i}: {mask_base64_fields(msg)}")
                                if isinstance(msg.get("content"), list):
                                    for j, content_item in enumerate(msg.get("content", [])):
                                        if isinstance(content_item, dict):
                                            content_keys = list(content_item.keys())
                                            print(f"  内容项 {j}: 类型={content_keys}")
                                            if "audio" in content_item and not content_item["audio"].startswith("data:"):
                                                print(f"  音频文件路径: {content_item['audio']}")
                            
                            # 添加代理设置（如果需要）
                            # os.environ['http_proxy'] = 'http://127.0.0.1:7890'
                            # os.environ['https_proxy'] = 'http://127.0.0.1:7890'
                            
                            # 尝试发送请求
                            try:
                                completion = client.chat.completions.create(**completion_args)
                                print("请求成功发送，开始处理响应流...")
                            except Exception as e:
                                # 如果是音频文件路径问题，尝试使用dashscope原生API
                                if "audio" in str(e) and model_to_use == "qwen-audio-turbo-latest":
                                    print("尝试使用dashscope原生API发送请求...")
                                    try:
                                        from dashscope import MultiModalConversation
                                        
                                        # 设置API密钥
                                        os.environ["DASHSCOPE_API_KEY"] = dashscope_api_key
                                        
                                        # 转换为dashscope格式
                                        dashscope_messages = []
                                        
                                        # 处理每条消息
                                        for msg in completion_args["messages"]:
                                            new_msg = {"role": msg["role"], "content": []}
                                            
                                            # 处理内容
                                            for content_item in msg.get("content", []):
                                                if isinstance(content_item, dict):
                                                    # 确保每个内容项都有type字段
                                                    if "audio" in content_item and "type" not in content_item:
                                                        content_item["type"] = "audio"
                                                    if "text" in content_item and "type" not in content_item:
                                                        content_item["type"] = "text"
                                                    new_msg["content"].append(content_item)
                                            
                                            dashscope_messages.append(new_msg)
                                        
                                        # 发送请求
                                        response = MultiModalConversation.call(
                                            model=model_to_use,
                                            messages=dashscope_messages
                                        )
                                        
                                        # 模拟OpenAI格式的响应
                                        if response.status_code == 200:
                                            print("dashscope原生API请求成功")
                                            # 直接返回结果，不再使用流式处理
                                            # 获取响应文本
                                            if response.output and response.output.choices and response.output.choices[0].message.content:
                                                text = response.output.choices[0].message.content[0].get("text", "")
                                                
                                                # 直接返回结果，不使用流式处理
                                                print(f"音频处理结果: {text[:100]}...")
                                                
                                                # 使用非流式方式返回结果
                                                # 保存对话历史
                                                memory.chat_memory.add_user_message(str(message))
                                                memory.chat_memory.add_ai_message(text)
                                                
                                                # 保存到Redis
                                                conversation_history = []
                                                for msg in memory.chat_memory.messages:
                                                    if hasattr(msg, "content"):
                                                        msg_type = "human" if msg.type == "human" else "ai"
                                                        conversation_history.append({"type": msg_type, "content": msg.content})
                                                
                                                redis_client.set(memory_key, json.dumps(conversation_history))
                                                redis_client.expire(memory_key, 60 * 60 * 24 * 7)  # Expire after 1 week
                                                
                                                return jsonify({
                                                    "response": text,
                                                    "conversation_id": conversation_id
                                                })
                                            else:
                                                raise Exception("响应中没有内容")
                                        else:
                                            error_msg = f"dashscope返回错误: {response.code} - {response.message}"
                                            print(error_msg)
                                            return jsonify({
                                                "error": error_msg,
                                                "response": "抱歉，音频处理失败。请稍后再试或尝试使用不同的音频文件。"
                                            })
                                    except ImportError:
                                        print("dashscope模块未安装，无法使用原生API")
                                        raise e
                                    except Exception as dse:
                                        print(f"dashscope原生API调用失败: {str(dse)}")
                                        raise e
                                else:
                                    raise e
                        except Exception as req_err:
                            error_msg = str(req_err)
                            print(f"发送请求时出错: {error_msg}")
                            
                            # 根据错误类型提供更具体的错误信息
                            if "Connection error" in error_msg:
                                error_detail = "连接错误：无法连接到API服务器。请检查网络连接或尝试使用代理。"
                            elif "Unauthorized" in error_msg or "Invalid authentication" in error_msg:
                                error_detail = "授权错误：API密钥无效或已过期。请更新API密钥。"
                            elif "Rate limit" in error_msg:
                                error_detail = "速率限制：请求过于频繁，请稍后再试。"
                            elif "Bad gateway" in error_msg:
                                error_detail = "服务器错误：API服务器暂时不可用，请稍后再试。"
                            else:
                                error_detail = f"请求处理失败，请检查API密钥和请求格式。错误: {error_msg}"
                            
                            yield f"data: {json.dumps({'error': f'发送请求失败: {error_msg}'})}\n\n"
                            yield f"data: {json.dumps({'chunk': error_detail})}\n\n"
                            yield f"data: {json.dumps({'done': True})}\n\n"
                            return
                        
                        response_received = False
                        
                        for chunk in completion:
                            response_received = True
                            print(f"收到响应块类型: {type(chunk)}")
                            
                            # 尝试打印完整的响应对象
                            try:
                                import json as json_lib
                                chunk_dict = chunk.model_dump() if hasattr(chunk, 'model_dump') else vars(chunk)
                                print(f"响应块内容: {json_lib.dumps(mask_base64_fields(chunk_dict), default=str)}")
                            except Exception as e:
                                print(f"无法序列化响应块: {e}")
                            
                            if chunk.choices and hasattr(chunk.choices[0], 'delta'):
                                delta = chunk.choices[0].delta
                                if hasattr(delta, 'content') and delta.content:
                                    print(f"内容块: {mask_base64_fields(delta.content)}")
                                    yield f"data: {json.dumps({'chunk': mask_base64_fields(delta.content)})}\n\n"
                                    full_response.append(delta.content)
                                else:
                                    print(f"Delta没有content属性或为空: {delta}")
                            elif hasattr(chunk, 'choices') and chunk.choices:
                                # 尝试直接从choices获取内容
                                for choice in chunk.choices:
                                    if hasattr(choice, 'message') and hasattr(choice.message, 'content'):
                                        content = choice.message.content
                                        if content:
                                            print(f"从message中获取内容: {content}")
                                            yield f"data: {json.dumps({'chunk': content})}\n\n"
                                            full_response.append(content)
                            elif hasattr(chunk, 'usage'):
                                # 处理使用情况统计
                                print(f"API使用情况: {chunk.usage}")
                            else:
                                print(f"未知的响应块类型: {chunk}")
                        
                        if not response_received:
                            print("未收到任何响应")
                            yield f"data: {json.dumps({'chunk': '未收到模型响应，请检查API密钥和请求格式。'})}\n\n"
                            
                        # 完成后发送done事件
                        full_text = "".join(full_response)
                        yield f"data: {json.dumps({'done': True, 'full_response': full_text})}\n\n"
                        
                        # 保存对话历史
                        memory.chat_memory.add_user_message(str(message))
                        memory.chat_memory.add_ai_message(full_text)
                        
                        # 保存到Redis
                        conversation_history = []
                        for msg in memory.chat_memory.messages:
                            if hasattr(msg, "content"):
                                msg_type = "human" if msg.type == "human" else "ai"
                                conversation_history.append({"type": msg_type, "content": msg.content})
                        
                        redis_client.set(memory_key, json.dumps(conversation_history))
                        redis_client.expire(memory_key, 60 * 60 * 24 * 7)  # Expire after 1 week
                    
                    except Exception as e:
                        yield f"data: {json.dumps({'error': str(e)})}\n\n"
                
                return Response(
                    stream_with_context(generate_qwen_omni()),
                    mimetype='text/event-stream',
                    headers={
                        'Cache-Control': 'no-cache',
                        'Connection': 'keep-alive',
                        'X-Accel-Buffering': 'no'
                    }
                )
            else:
                # 处理非流式响应
                try:
                    print("开始发送非流式请求到Qwen-Omni-Turbo...")
                    completion = client.chat.completions.create(**completion_args)
                    
                    # 打印完整响应
                    try:
                        import json as json_lib
                        resp_dict = completion.model_dump() if hasattr(completion, 'model_dump') else vars(completion)
                        print(f"完整响应: {json_lib.dumps(mask_base64_fields(resp_dict), default=str)}")
                    except Exception as e:
                        print(f"无法序列化响应: {e}")
                    
                    if hasattr(completion, 'choices') and completion.choices:
                        if hasattr(completion.choices[0], 'message') and hasattr(completion.choices[0].message, 'content'):
                            response = completion.choices[0].message.content
                            print(f"成功获取响应内容: {response[:100]}...")
                        else:
                            print(f"警告: choices中没有message.content: {completion.choices}")
                            response = "抱歉，模型返回了空响应。请尝试重新提问或检查服务器日志。"
                    else:
                        print(f"警告: 响应中没有choices: {completion}")
                        response = "抱歉，无法处理响应。请检查服务器日志以获取更多信息。"
                except Exception as e:
                    print(f"处理非流式响应时出错: {str(e)}")
                    response = f"处理失败: {str(e)}"
                
                # 保存对话历史
                memory.chat_memory.add_user_message(str(message))
                memory.chat_memory.add_ai_message(response)
                
                # 保存到Redis
                conversation_history = []
                for msg in memory.chat_memory.messages:
                    if hasattr(msg, "content"):
                        msg_type = "human" if msg.type == "human" else "ai"
                        conversation_history.append({"type": msg_type, "content": msg.content})
                
                redis_client.set(memory_key, json.dumps(conversation_history))
                redis_client.expire(memory_key, 60 * 60 * 24 * 7)  # Expire after 1 week
                
                return jsonify({
                    "response": response,
                    "conversation_id": conversation_id
                })
                
        except Exception as e:
            print(f"Qwen-Omni-Turbo处理错误: {str(e)}")
            return jsonify({"error": f"处理失败: {str(e)}"}), 500
    else:
        # 常规处理流程
        template = """The following is a friendly conversation between a human and an AI assistant.
        The AI assistant is helpful, creative, clever, and very friendly.
        
        Current conversation:
        {history}
        Human: {input}
        AI Assistant:"""
        
        prompt = PromptTemplate(
            input_variables=["history", "input"],
            template=template
        )
        
        conversation = ConversationChain(
            llm=llm,
            memory=memory,
            prompt=prompt,
            verbose=True
        )
        
        # Process image if included
        if "image_url" in data:
            # For models that support image input, we'd add the image URL to the prompt
            image_prompt = f"\n[User has shared an image. Please analyze this image: {data['image_url']}]"
            
            if stream:
                return stream_response(conversation, message + image_prompt, memory, memory_key)
            else:
                response = conversation.predict(input=message + image_prompt)
        else:
            if stream:
                return stream_response(conversation, message, memory, memory_key)
            else:
                response = conversation.predict(input=message)
    
    # 以下代码仅在非流式模式下执行
    if not stream:
        # Save updated conversation history to Redis
        conversation_history = []
        for msg in memory.chat_memory.messages:
            if hasattr(msg, "content"):
                msg_type = "human" if msg.type == "human" else "ai"
                conversation_history.append({"type": msg_type, "content": msg.content})
        
        redis_client.set(memory_key, json.dumps(conversation_history))
        redis_client.expire(memory_key, 60 * 60 * 24 * 7)  # Expire after 1 week
        
        return jsonify({
            "response": response,
            "conversation_id": conversation_id
        })

def stream_response(conversation, message, memory, memory_key):
    """处理流式响应"""
    
    # 使用支持流式输出的模型
    try:
        llm = get_llm(conversation.llm.model_name, streaming=True)
        conversation.llm = llm
    except Exception as e:
        print(f"设置流式模型失败: {str(e)}")
        # 发送错误事件
        def error_generator():
            yield f"data: {json.dumps({'error': f'设置流式模型失败: {str(e)}'})}\n\n"
        
        return Response(
            stream_with_context(error_generator()),
            mimetype='text/event-stream',
            headers={
                'Cache-Control': 'no-cache',
                'Connection': 'keep-alive',
                'X-Accel-Buffering': 'no'
            }
        )
    
    def generate():
        # 用于保存完整的响应
        full_response = []
        
        # 使用异步调用获取响应流
        try:
            # 使用langchain的流式输出接口
            for chunk in conversation.predict(input=message):
                # 确保chunk是字符串
                if chunk is None:
                    chunk = ""
                elif not isinstance(chunk, str):
                    chunk = str(chunk)
                
                # 发送数据块
                full_response.append(chunk)
                yield f"data: {json.dumps({'chunk': chunk})}\n\n"
                
            # 完成后保存对话历史
            conversation_history = []
            full_text = "".join(full_response)
            
            try:
                # 手动将用户消息和AI响应添加到记忆中
                memory.chat_memory.add_user_message(message)
                memory.chat_memory.add_ai_message(full_text)
                
                # 保存到Redis
                for msg in memory.chat_memory.messages:
                    if hasattr(msg, "content"):
                        msg_type = "human" if msg.type == "human" else "ai"
                        conversation_history.append({"type": msg_type, "content": msg.content})
                
                # 使用提供的memory_key保存对话历史
                print(f"保存对话历史到: {memory_key}")
                redis_client.set(memory_key, json.dumps(conversation_history))
                redis_client.expire(memory_key, 60 * 60 * 24 * 7)  # Expire after 1 week
            except Exception as e:
                print(f"保存对话历史失败: {str(e)}")
                # 这里不抛出异常，继续返回响应
            
            # 发送完成事件
            yield f"data: {json.dumps({'done': True, 'full_response': full_text})}\n\n"
        except Exception as e:
            print(f"流式输出处理异常: {str(e)}")
            # 发送错误事件
            yield f"data: {json.dumps({'error': str(e)})}\n\n"
    
    return Response(
        stream_with_context(generate()),
        mimetype='text/event-stream',
        headers={
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no'  # 禁用Nginx缓冲
        }
    )

# 添加一个流式聊天的新API端点，方便前端调用
@app.route('/api/chat/stream', methods=['POST'])
@jwt_required()
def chat_stream():
    # 检查是否是JSON请求还是表单请求
    if request.is_json:
        data = request.get_json()
        data['stream'] = True  # 强制使用流式输出
    else:
        # 表单请求处理
        request.form = request.form.copy()
        request.form['stream'] = 'true'  # 强制使用流式输出
    
    return chat()  # 复用现有的chat函数

@app.route('/api/conversations', methods=['GET'])
@jwt_required()
def get_conversations():
    current_user = get_jwt_identity()
    page_type = request.args.get('page_type', 'chat')  # 获取功能类型参数
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    # Get all conversation keys for this user and specific page type
    conversation_keys = redis_client.keys(f"chat:{user_id}:{page_type}:*")
    conversations = []
    
    for key in conversation_keys:
        # 现在key格式为 chat:user_id:page_type:conversation_id
        conversation_id = key.decode('utf-8').split(':')[3]
        try:
            conversation_data = json.loads(redis_client.get(key))
            
            # Get first few messages for title
            title = "New Conversation"
            if len(conversation_data) > 0:
                first_message = conversation_data[0]["content"]
                title = first_message[:30] + "..." if len(first_message) > 30 else first_message
            
            conversations.append({
                "id": conversation_id,
                "title": title,
                "messages": conversation_data,
                "page_type": page_type
            })
        except Exception as e:
            print(f"处理对话记录时出错 (key={key}): {str(e)}")
            # 如果某个对话记录损坏，跳过它而不是整个失败
            continue
    
    # 按时间排序（假设最新的对话ID较大）
    conversations.sort(key=lambda x: x["id"], reverse=True)
    
    return jsonify(conversations)

@app.route('/api/conversations/<conversation_id>', methods=['DELETE'])
@jwt_required()
def delete_conversation(conversation_id):
    current_user = get_jwt_identity()
    page_type = request.args.get('page_type', 'chat')  # 获取功能类型参数
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    # Delete conversation from Redis
    memory_key = f"chat:{user_id}:{page_type}:{conversation_id}"
    print(f"尝试删除对话: {memory_key}")
    if redis_client.exists(memory_key):
        redis_client.delete(memory_key)
        return jsonify({"message": "Conversation deleted successfully"})
    else:
        return jsonify({"error": "Conversation not found"}), 404

# Knowledge base routes
@app.route('/api/knowledge/upload', methods=['POST'])
@jwt_required()
def upload_knowledge():
    current_user = get_jwt_identity()
    
    if 'file' not in request.files:
        return jsonify({"error": "No file provided"}), 400
    
    file = request.files['file']
    kb_name = request.form.get('name', 'Knowledge Base')
    kb_description = request.form.get('description', '')
    
    if file.filename == '':
        return jsonify({"error": "No file selected"}), 400
    
    # 检查文件类型
    allowed_extensions = {'pdf', 'txt', 'docx'}
    file_extension = file.filename.split('.')[-1].lower() if '.' in file.filename else ''
    
    if file_extension not in allowed_extensions:
        return jsonify({"error": "Only PDF, TXT, and DOCX files are allowed"}), 400
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Create knowledge base record
            cursor.execute(
                "INSERT INTO knowledge_bases (user_id, name, description) VALUES (%s, %s, %s)",
                (user_id, kb_name, kb_description)
            )
            conn.commit()
            kb_id = cursor.lastrowid
    finally:
        conn.close()
    
    # 处理文件内容，根据文件类型采用不同的处理方式
    file_content = ""
    
    try:
        # PDF文件处理
        if file_extension == 'pdf':
            import PyPDF2
            from io import BytesIO
            
            try:
                pdf_reader = PyPDF2.PdfReader(BytesIO(file.read()))
                for page_num in range(len(pdf_reader.pages)):
                    page = pdf_reader.pages[page_num]
                    file_content += page.extract_text() + "\n\n"
            except Exception as e:
                print(f"PDF处理错误: {e}")
                # 如果PyPDF2失败，尝试使用pdfplumber
                try:
                    import pdfplumber
                    with pdfplumber.open(BytesIO(file.stream.read())) as pdf:
                        for page in pdf.pages:
                            text = page.extract_text()
                            if text:
                                file_content += text + "\n\n"
                except Exception as e2:
                    print(f"pdfplumber处理错误: {e2}")
                    return jsonify({"error": f"无法处理PDF文件: {str(e2)}"}), 500
        
        # Word文档处理
        elif file_extension == 'docx':
            try:
                import docx
                from io import BytesIO
                
                doc = docx.Document(BytesIO(file.read()))
                for para in doc.paragraphs:
                    file_content += para.text + "\n"
            except Exception as e:
                print(f"Word文档处理错误: {e}")
                return jsonify({"error": f"无法处理Word文档: {str(e)}"}), 500
        
        # 文本文件处理
        elif file_extension == 'txt':
            try:
                # 尝试不同的编码方式
                encodings = ['utf-8', 'latin-1', 'gbk', 'gb2312', 'gb18030', 'big5']
                for encoding in encodings:
                    try:
                        file.stream.seek(0)  # 重置文件指针
                        file_content = file.read().decode(encoding)
                        break
                    except UnicodeDecodeError:
                        continue
                
                if not file_content:
                    return jsonify({"error": "无法解码文本文件，请检查文件编码"}), 500
            except Exception as e:
                print(f"文本文件处理错误: {e}")
                return jsonify({"error": f"处理文本文件时出错: {str(e)}"}), 500
        
        # 检查是否成功提取到内容
        if not file_content or len(file_content.strip()) < 10:
            return jsonify({"error": "文件内容为空或无法提取文本"}), 400
        
        # 分割文本
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200
        )
        chunks = text_splitter.split_text(file_content)
        
        if not chunks:
            return jsonify({"error": "无法从文件中提取有效文本块"}), 400
        
        print(f"成功从{file.filename}中提取了{len(chunks)}个文本块")
        
        # 为每个文本块添加元数据
        texts = chunks
        metadatas = [{"source": file.filename, "kb_id": str(kb_id), "chunk_id": i} for i in range(len(chunks))]
        
        try:
            # 使用FAISS作为主要向量存储
            from langchain_community.vectorstores import FAISS
            import os
            
            # 创建存储目录
            os.makedirs("faiss_indexes", exist_ok=True)
            
            # 创建FAISS向量存储
            vector_store = FAISS.from_texts(
                texts=texts,
                embedding=embeddings,
                metadatas=metadatas
            )
            
            # 保存到本地文件 - 两份拷贝
            # 1. 知识库特定索引
            vector_store.save_local(f"faiss_indexes/kb_{kb_id}")
            
            # 2. 合并到主索引（如果存在）
            try:
                main_index_path = "faiss_indexes/knowledge_base"
                if os.path.exists(main_index_path):
                    main_vector_store = FAISS.load_local(
                        main_index_path, 
                        embeddings,
                        allow_dangerous_deserialization=True  # 添加安全参数
                    )
                    # 合并当前向量存储到主向量存储
                    main_vector_store.merge_from(vector_store)
                    main_vector_store.save_local(main_index_path)
                    print(f"成功将知识库 {kb_id} 合并到主向量存储")
                else:
                    # 如果主索引不存在，直接保存当前索引作为主索引
                    vector_store.save_local(main_index_path)
                    print(f"创建主向量存储，使用知识库 {kb_id}")
            except Exception as e:
                print(f"合并到主向量存储失败: {e}")
                # 即使合并失败，仍然保留了知识库特定的索引
            
            print(f"使用FAISS本地向量存储创建成功: kb_{kb_id}")
            
            return jsonify({
                "message": "知识库创建成功",
                "kb_id": kb_id,
                "chunks_count": len(chunks)
            })
            
        except Exception as e:
            print(f"向量存储创建失败: {e}")
            return jsonify({"error": f"向量存储创建失败: {str(e)}"}), 500
        
    except Exception as e:
        print(f"文件处理总体错误: {e}")
        return jsonify({"error": f"处理文件时出错: {str(e)}"}), 500

# 知识库问答函数
@app.route('/api/knowledge/query', methods=['POST'])
@jwt_required()
def query_knowledge():
    current_user = get_jwt_identity()
    data = request.get_json()
    query = data.get('query')
    kb_id = data.get('kb_id')
    
    if not query:
        return jsonify({"error": "查询内容不能为空"}), 400
    
    # 获取用户ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "用户不存在"}), 404
            user_id = user["id"]
            
            # 如果指定了知识库ID，检查是否存在
            if kb_id:
                cursor.execute("SELECT id FROM knowledge_bases WHERE id = %s AND user_id = %s", (kb_id, user_id))
                kb = cursor.fetchone()
                if not kb:
                    return jsonify({"error": "找不到指定的知识库或无权限访问"}), 404
    finally:
        conn.close()
    
    try:
        # 初始化向量存储
        vector_store = get_vector_store()
        
        # 如果没有向量存储或使用了假嵌入，直接使用模型回答
        if vector_store is None:
            llm = get_llm()
            response = llm.invoke(f"以下是一个问题，请尽可能回答：{query}")
            return jsonify({
                "response": response,
                "message": "注意：由于向量存储问题，使用了一般问答方式"
            })
        
        # 检查是否有特定知识库的向量
        kb_specific_index_path = f"faiss_indexes/kb_{kb_id}"
        if os.path.exists(kb_specific_index_path):
            try:
                from langchain_community.vectorstores import FAISS
                kb_vector_store = FAISS.load_local(
                    kb_specific_index_path, 
                    embeddings,
                    allow_dangerous_deserialization=True  # 添加安全参数
                )
                print(f"使用知识库特定的向量存储: {kb_id}")
                vector_store = kb_vector_store
            except Exception as e:
                print(f"加载知识库特定向量存储失败: {e}，使用通用向量存储")
        
        # 正常知识库查询
        try:
            # 尝试使用过滤器查询特定知识库
            relevant_documents = vector_store.similarity_search(
                query, 
                k=5,  # 增加检索数量
                filter={"kb_id": str(kb_id)} if kb_id else None
            )
        except Exception as e:
            print(f"过滤查询失败: {e}，尝试不使用过滤器")
            relevant_documents = vector_store.similarity_search(query, k=5)  # 增加检索数量
        
        if not relevant_documents:
            return jsonify({
                "response": "抱歉，我在知识库中没有找到与您问题相关的信息。",
                "sources": []
            })
        
        # 构建上下文信息
        context_parts = []
        for i, doc in enumerate(relevant_documents):
            # 为每个文档添加编号和来源信息
            source_info = f"来源: {doc.metadata.get('source', '未知')}" if doc.metadata and 'source' in doc.metadata else ""
            context_parts.append(f"[文档{i+1}] {doc.page_content}\n{source_info}")
        
        context = "\n\n".join(context_parts)
        
        # 使用优化的提示词模板
        llm = get_llm()
        prompt = f"""你是一个专业的知识库问答助手。请基于以下检索到的文档内容回答用户的问题。

用户问题: {query}

检索到的相关文档:
{context}

回答要求:
1. 仅使用检索到的文档中的信息回答问题，不要添加未在文档中提及的信息
2. 如果检索到的文档不包含回答问题所需的信息，请直接说明无法回答，不要编造信息
3. 回答要全面、准确，并尽可能引用文档中的原文
4. 如果多个文档内容存在冲突，请指出这些冲突并说明理由
5. 回答应该条理清晰、逻辑严谨，适当使用标点符号和段落分隔
6. 如果问题需要专业知识，请使用专业且准确的术语

请基于以上要求，回答用户问题:
"""
        response = llm.invoke(prompt)
        
        # 确保response是字符串
        if hasattr(response, 'content'):
            response_text = response.content
        else:
            response_text = str(response)
        
        # 准备文档源数据，确保可序列化
        sources_data = []
        for doc in relevant_documents:
            # 确保元数据是可序列化的
            metadata = {}
            if doc.metadata:
                for key, value in doc.metadata.items():
                    # 只保留基本类型的元数据
                    if isinstance(value, (str, int, float, bool)) or value is None:
                        metadata[key] = value
            
            sources_data.append({
                "content": doc.page_content,
                "metadata": metadata
            })
        
        return jsonify({
            "response": response_text,
            "sources": sources_data
        })
    except Exception as e:
        print(f"知识库查询错误: {e}")
        return jsonify({"error": str(e)}), 500

# 流式知识库查询
@app.route('/api/knowledge/query/stream', methods=['POST'])
@jwt_required()
def query_knowledge_stream():
    current_user = get_jwt_identity()
    data = request.get_json()
    query = data.get('query')
    kb_id = data.get('kb_id')
    
    if not query:
        return jsonify({"error": "Query is required"}), 400
    
    if not kb_id:
        return jsonify({"error": "Knowledge base ID is required"}), 400
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Check if knowledge base exists and belongs to user
            cursor.execute("SELECT id FROM knowledge_bases WHERE id = %s AND user_id = %s", (kb_id, user_id))
            kb = cursor.fetchone()
            if not kb:
                return jsonify({"error": "Knowledge base not found or access denied"}), 404
    finally:
        conn.close()
    
    # Get vector store
    vector_store = get_vector_store()
    if not vector_store:
        return jsonify({"error": "Vector store not available"}), 500
    
    def generate():
        try:
            # 使用支持流式输出的LLM
            llm = get_llm(streaming=True)
            
            # 检查是否有特定知识库的向量
            kb_specific_index_path = f"faiss_indexes/kb_{kb_id}"
            if os.path.exists(kb_specific_index_path):
                try:
                    from langchain_community.vectorstores import FAISS
                    kb_vector_store = FAISS.load_local(
                        kb_specific_index_path, 
                        embeddings,
                        allow_dangerous_deserialization=True  # 添加安全参数
                    )
                    print(f"使用知识库特定的向量存储: {kb_id}")
                    vector_store_to_use = kb_vector_store
                except Exception as e:
                    print(f"加载知识库特定向量存储失败: {e}，使用通用向量存储")
                    vector_store_to_use = vector_store
            else:
                vector_store_to_use = vector_store
            
            # 查询知识库
            try:
                # 尝试使用过滤器查询特定知识库
                docs = vector_store_to_use.similarity_search(
                    query, 
                    k=5,  # 增加检索数量
                    filter={"kb_id": str(kb_id)} if kb_id else None
                )
            except Exception as e:
                print(f"过滤查询失败: {e}，尝试不使用过滤器")
                docs = vector_store_to_use.similarity_search(query, k=5)  # 增加检索数量
                
            if not docs:
                yield f"data: {json.dumps({'chunk': '抱歉，我在知识库中没有找到相关信息。'})}\n\n"
                yield f"data: {json.dumps({'done': True, 'full_response': '抱歉，我在知识库中没有找到相关信息。'})}\n\n"
                return
            
            # 构建上下文，为每个文档添加编号和来源信息
            context_parts = []
            for i, doc in enumerate(docs):
                source_info = f"来源: {doc.metadata.get('source', '未知')}" if doc.metadata and 'source' in doc.metadata else ""
                context_parts.append(f"[文档{i+1}] {doc.page_content}\n{source_info}")
            
            context = "\n\n".join(context_parts)
            
            # 使用优化的提示词模板
            prompt = f"""你是一个专业的知识库问答助手。请基于以下检索到的文档内容回答用户的问题。

用户问题: {query}

检索到的相关文档:
{context}

回答要求:
1. 仅使用检索到的文档中的信息回答问题，不要添加未在文档中提及的信息
2. 如果检索到的文档不包含回答问题所需的信息，请直接说明无法回答，不要编造信息
3. 回答要全面、准确，并尽可能引用文档中的原文
4. 如果多个文档内容存在冲突，请指出这些冲突并说明理由
5. 回答应该条理清晰、逻辑严谨，适当使用标点符号和段落分隔
6. 如果问题需要专业知识，请使用专业且准确的术语

请基于以上要求，回答用户问题:
"""
            
            # 使用流式输出
            full_response = []
            for chunk in llm.stream(prompt):
                if chunk:
                    # 确保chunk是字符串
                    chunk_text = chunk.content if hasattr(chunk, 'content') else str(chunk)
                    full_response.append(chunk_text)
                    yield f"data: {json.dumps({'chunk': chunk_text})}\n\n"
            
            # 发送完成事件
            yield f"data: {json.dumps({'done': True, 'full_response': ''.join(full_response)})}\n\n"
            
        except Exception as e:
            print(f"知识库查询流输出错误: {str(e)}")
            yield f"data: {json.dumps({'error': str(e)})}\n\n"
    
    return Response(
        stream_with_context(generate()),
        mimetype='text/event-stream',
        headers={
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no'
        }
    )

@app.route('/api/knowledge/bases', methods=['GET'])
@jwt_required()
def get_knowledge_bases():
    current_user = get_jwt_identity()
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Get all knowledge bases for this user
            cursor.execute(
                "SELECT id, name, description, created_at FROM knowledge_bases WHERE user_id = %s",
                (user_id,)
            )
            bases = cursor.fetchall()
            
            # Convert datetime objects to strings
            for base in bases:
                base["created_at"] = base["created_at"].strftime("%Y-%m-%d %H:%M:%S")
            
            return jsonify(bases)
    finally:
        conn.close()

# Task management routes
@app.route('/api/tasks', methods=['GET'])
@jwt_required()
def get_tasks():
    current_user = get_jwt_identity()
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Get all tasks for this user
            cursor.execute(
                "SELECT id, title, description, due_date, status, created_at FROM tasks WHERE user_id = %s",
                (user_id,)
            )
            tasks = cursor.fetchall()
            
            # Convert datetime objects to strings
            for task in tasks:
                if task["due_date"]:
                    task["due_date"] = task["due_date"].strftime("%Y-%m-%d %H:%M:%S")
                task["created_at"] = task["created_at"].strftime("%Y-%m-%d %H:%M:%S")
            
            return jsonify(tasks)
    finally:
        conn.close()

@app.route('/api/tasks', methods=['POST'])
@jwt_required()
def create_task():
    current_user = get_jwt_identity()
    data = request.get_json()
    
    title = data.get('title')
    description = data.get('description', '')
    due_date_str = data.get('due_date')
    send_email = data.get('send_email', False)
    
    if not title:
        return jsonify({"error": "Task title is required"}), 400
    
    # Parse due date if provided
    due_date = None
    if due_date_str:
        try:
            due_date = datetime.datetime.strptime(due_date_str, "%Y-%m-%d %H:%M:%S")
        except ValueError:
            return jsonify({"error": "Invalid date format. Use YYYY-MM-DD HH:MM:SS"}), 400
    
    # Get user ID and email
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id, email FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            user_email = user["email"]
            
            # Create task
            cursor.execute(
                "INSERT INTO tasks (user_id, title, description, due_date) VALUES (%s, %s, %s, %s)",
                (user_id, title, description, due_date)
            )
            conn.commit()
            task_id = cursor.lastrowid
            
            # Send email notification if requested
            if send_email and user_email:
                try:
                    send_task_email(user_email, title, description, due_date_str)
                except Exception as e:
                    # Log error but don't fail the request
                    print(f"Email sending failed: {str(e)}")
            
            return jsonify({
                "message": "Task created successfully",
                "task_id": task_id
            }), 201
    finally:
        conn.close()

@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
@jwt_required()
def update_task(task_id):
    current_user = get_jwt_identity()
    data = request.get_json()
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Check if task exists and belongs to user
            cursor.execute(
                "SELECT id FROM tasks WHERE id = %s AND user_id = %s",
                (task_id, user_id)
            )
            task = cursor.fetchone()
            if not task:
                return jsonify({"error": "Task not found"}), 404
            
            # Update fields
            fields = []
            values = []
            
            if 'title' in data:
                fields.append("title = %s")
                values.append(data['title'])
            
            if 'description' in data:
                fields.append("description = %s")
                values.append(data['description'])
            
            if 'due_date' in data:
                if data['due_date']:
                    try:
                        due_date = datetime.datetime.strptime(data['due_date'], "%Y-%m-%d %H:%M:%S")
                        fields.append("due_date = %s")
                        values.append(due_date)
                    except ValueError:
                        return jsonify({"error": "Invalid date format. Use YYYY-MM-DD HH:MM:SS"}), 400
                else:
                    fields.append("due_date = NULL")
            
            if 'status' in data:
                fields.append("status = %s")
                values.append(data['status'])
            
            if not fields:
                return jsonify({"error": "No fields to update"}), 400
            
            # Execute update
            query = f"UPDATE tasks SET {', '.join(fields)} WHERE id = %s"
            values.append(task_id)
            
            cursor.execute(query, values)
            conn.commit()
            
            return jsonify({"message": "Task updated successfully"})
    finally:
        conn.close()

@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
@jwt_required()
def delete_task(task_id):
    current_user = get_jwt_identity()
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "User not found"}), 404
            user_id = user["id"]
            
            # Check if task exists and belongs to user
            cursor.execute(
                "SELECT id FROM tasks WHERE id = %s AND user_id = %s",
                (task_id, user_id)
            )
            task = cursor.fetchone()
            if not task:
                return jsonify({"error": "Task not found"}), 404
            
            # Delete task
            cursor.execute("DELETE FROM tasks WHERE id = %s", (task_id,))
            conn.commit()
            
            return jsonify({"message": "Task deleted successfully"})
    finally:
        conn.close()

# Weather API
@app.route('/api/weather', methods=['GET'])
def get_weather():
    city = request.args.get('city')
    
    if not city:
        return jsonify({"error": "请提供城市名称"}), 400
    
    api_key = os.getenv("AMAP_API_KEY")
    if not api_key:
        return jsonify({"error": "天气API密钥未配置"}), 500
    
    try:
        print(f"请求城市天气：{city}，使用的API Key：{api_key}")
        
        # 1. 使用地理编码API获取城市编码
        geocode_url = "https://restapi.amap.com/v3/geocode/geo"
        geocode_params = {
            "address": city,
            "key": api_key,
            "output": "JSON"  # 明确指定输出格式
        }
        
        print(f"地理编码请求URL: {geocode_url}，参数: {geocode_params}")
        geocode_response = requests.get(geocode_url, params=geocode_params)
        geocode_data = geocode_response.json()
        
        print(f"地理编码API返回: {geocode_data}")
        
        if geocode_data.get("status") == "1" and geocode_data.get("geocodes") and len(geocode_data.get("geocodes")) > 0:
            # 获取城市编码和名称
            adcode = geocode_data["geocodes"][0]["adcode"]
            city_name = geocode_data["geocodes"][0]["formatted_address"]
            
            print(f"获取到城市编码: {adcode}, 城市名称: {city_name}")
            
            # 2. 直接使用城市编码获取天气信息
            weather_url = "https://restapi.amap.com/v3/weather/weatherInfo"
            
            # 获取实时天气
            live_params = {
                "key": api_key,
                "city": adcode,
                "extensions": "base",
                "output": "JSON"  # 明确指定输出格式
            }
            
            print(f"实时天气请求URL: {weather_url}，参数: {live_params}")
            live_response = requests.get(weather_url, params=live_params)
            live_data = live_response.json()
            
            print(f"实时天气API返回: {live_data}")
            
            # 获取天气预报
            forecast_params = {
                "key": api_key,
                "city": adcode,
                "extensions": "all",
                "output": "JSON"  # 明确指定输出格式
            }
            
            print(f"天气预报请求URL: {weather_url}，参数: {forecast_params}")
            forecast_response = requests.get(weather_url, params=forecast_params)
            forecast_data = forecast_response.json()
            
            print(f"天气预报API返回: {forecast_data}")
            
            # 检查返回数据
            if (live_data.get("status") == "1" and live_data.get("lives") and 
                forecast_data.get("status") == "1" and forecast_data.get("forecasts")):
                
                # 合并实时天气和预报数据
                result = {
                    "city": city_name,
                    "live": live_data["lives"][0] if live_data.get("lives") else {},
                    "casts": forecast_data["forecasts"][0]["casts"] if forecast_data.get("forecasts") and forecast_data["forecasts"][0].get("casts") else []
                }
                
                return jsonify(result)
            else:
                error_msg = "天气数据获取失败"
                if live_data.get("info"):
                    error_msg += f": {live_data.get('info')}"
                elif live_data.get("infocode"):
                    error_msg += f": 错误代码 {live_data.get('infocode')}"
                    
                print(f"天气API错误: {error_msg}")
                return jsonify({"error": f"无法获取{city}的天气数据: {error_msg}"}), 500
        else:
            error_msg = ""
            if geocode_data.get("info") and geocode_data.get("info") != "OK":
                error_msg = f": {geocode_data.get('info')}"
            elif geocode_data.get("infocode"):
                error_msg = f": 错误代码 {geocode_data.get('infocode')}"
                
            print(f"地理编码API错误: 找不到城市{city}{error_msg}")
            return jsonify({"error": f"找不到城市\"{city}\"{error_msg}，请尝试使用完整的城市名称，如\"北京市\"、\"上海市\"等"}), 404
    except Exception as e:
        print(f"天气API异常: {str(e)}")
        return jsonify({"error": f"天气API错误: {str(e)}"}), 500

# Helper function to send email
def send_task_email(to_email, task_title, task_description, due_date):
    smtp_server = os.getenv("SMTP_SERVER", "smtp.qq.com")
    smtp_port = int(os.getenv("SMTP_PORT", 587))
    smtp_username = os.getenv("SMTP_USERNAME")
    smtp_password = os.getenv("SMTP_PASSWORD")
    
    if not smtp_username or not smtp_password:
        raise Exception("SMTP credentials not configured")
    
    # Create message
    msg = MIMEMultipart()
    msg['From'] = smtp_username
    msg['To'] = to_email
    msg['Subject'] = f"Task Reminder: {task_title}"
    
    body = f"""
    <html>
    <body>
        <h2>Task Reminder</h2>
        <p><strong>Task:</strong> {task_title}</p>
        <p><strong>Description:</strong> {task_description}</p>
        <p><strong>Due Date:</strong> {due_date if due_date else 'Not specified'}</p>
    </body>
    </html>
    """
    
    msg.attach(MIMEText(body, 'html'))
    
    # Send email
    with smtplib.SMTP(smtp_server, smtp_port) as server:
        server.starttls()
        server.login(smtp_username, smtp_password)
        server.send_message(msg)

# 添加向量存储初始化函数
def get_vector_store():
    """初始化并返回向量存储实例"""
    if isinstance(embeddings, FakeEmbeddings):
        print("警告: 使用FakeEmbeddings，向量存储功能将受限")
    
    try:
        # 直接使用FAISS作为主要向量存储方案
        try:
            from langchain_community.vectorstores import FAISS
            import os
            
            # 检查是否有已存在的FAISS索引
            if not os.path.exists("faiss_indexes"):
                os.makedirs("faiss_indexes", exist_ok=True)
                print("创建FAISS索引目录")
                
                # 创建一个空的FAISS索引
                empty_texts = ["这是一个初始化文档，用于创建向量存储"]
                empty_metadatas = [{"source": "init", "kb_id": "0", "chunk_id": 0}]
                
                # 创建并保存空索引
                vector_store = FAISS.from_texts(
                    texts=empty_texts,
                    embedding=embeddings,
                    metadatas=empty_metadatas
                )
                vector_store.save_local("faiss_indexes/knowledge_base")
                print("创建了初始FAISS向量存储")
                return vector_store
            
            # 加载现有的FAISS索引
            if os.path.exists("faiss_indexes/knowledge_base"):
                vector_store = FAISS.load_local(
                    "faiss_indexes/knowledge_base", 
                    embeddings,
                    allow_dangerous_deserialization=True  # 添加安全参数
                )
                print("成功加载FAISS向量存储")
                return vector_store
            else:
                print("找不到FAISS索引文件")
                
                # 创建一个空的FAISS索引
                empty_texts = ["这是一个初始化文档，用于创建向量存储"]
                empty_metadatas = [{"source": "init", "kb_id": "0", "chunk_id": 0}]
                
                # 创建并保存空索引
                vector_store = FAISS.from_texts(
                    texts=empty_texts,
                    embedding=embeddings,
                    metadatas=empty_metadatas
                )
                vector_store.save_local("faiss_indexes/knowledge_base")
                print("创建了初始FAISS向量存储")
                return vector_store
            
        except Exception as e:
            print(f"FAISS向量存储初始化失败: {e}")
            return None
            
    except Exception as e:
        print(f"初始化向量存储失败: {e}")
        return None

# 管理员相关路由
@app.route('/api/admin/users', methods=['GET'])
@jwt_required()
def get_all_users():
    current_user = get_jwt_identity()
    
    # 检查当前用户是否是管理员
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT role FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user or user['role'] != 'admin':
                return jsonify({"error": "Unauthorized access"}), 403
            
            # 获取所有用户信息
            cursor.execute("SELECT id, username, email, role, created_at FROM users")
            users = cursor.fetchall()
            return jsonify(users), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

@app.route('/api/admin/users', methods=['POST'])
@jwt_required()
def create_user():
    current_user = get_jwt_identity()
    data = request.get_json()
    
    # 验证必要字段
    required_fields = ['username', 'password', 'email', 'role']
    if not all(field in data for field in required_fields):
        return jsonify({"error": "Missing required fields"}), 400
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            # 检查当前用户是否是管理员
            cursor.execute("SELECT role FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user or user['role'] != 'admin':
                return jsonify({"error": "Unauthorized access"}), 403
            
            # 检查用户名是否已存在
            cursor.execute("SELECT id FROM users WHERE username = %s", (data['username'],))
            if cursor.fetchone():
                return jsonify({"error": "Username already exists"}), 409
            
            # 创建新用户
            cursor.execute(
                "INSERT INTO users (username, password, email, role) VALUES (%s, %s, %s, %s)",
                (data['username'], data['password'], data['email'], data['role'])
            )
            conn.commit()
            
            # 返回新创建的用户信息
            cursor.execute(
                "SELECT id, username, email, role, created_at FROM users WHERE username = %s",
                (data['username'],)
            )
            new_user = cursor.fetchone()
            return jsonify(new_user), 201
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

@app.route('/api/admin/users/<int:user_id>', methods=['PUT'])
@jwt_required()
def update_user(user_id):
    current_user = get_jwt_identity()
    data = request.get_json()
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            # 检查当前用户是否是管理员
            cursor.execute("SELECT role FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user or user['role'] != 'admin':
                return jsonify({"error": "Unauthorized access"}), 403
            
            # 更新用户信息
            update_fields = []
            update_values = []
            
            if 'username' in data:
                update_fields.append("username = %s")
                update_values.append(data['username'])
            
            if 'email' in data:
                update_fields.append("email = %s")
                update_values.append(data['email'])
            
            if 'role' in data:
                update_fields.append("role = %s")
                update_values.append(data['role'])
            
            if not update_fields:
                return jsonify({"error": "No fields to update"}), 400
            
            update_values.append(user_id)
            query = f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s"
            cursor.execute(query, tuple(update_values))
            
            if cursor.rowcount == 0:
                return jsonify({"error": "User not found"}), 404
            
            conn.commit()
            
            # 返回更新后的用户信息
            cursor.execute(
                "SELECT id, username, email, role, created_at FROM users WHERE id = %s",
                (user_id,)
            )
            updated_user = cursor.fetchone()
            return jsonify(updated_user), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

@app.route('/api/admin/users/<int:user_id>', methods=['DELETE'])
@jwt_required()
def delete_user(user_id):
    current_user = get_jwt_identity()
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            # 检查当前用户是否是管理员
            cursor.execute("SELECT role FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user or user['role'] != 'admin':
                return jsonify({"error": "Unauthorized access"}), 403
            
            # 检查要删除的用户是否存在
            cursor.execute("SELECT username FROM users WHERE id = %s", (user_id,))
            target_user = cursor.fetchone()
            if not target_user:
                return jsonify({"error": "User not found"}), 404
            
            # 不允许删除自己
            if target_user['username'] == current_user:
                return jsonify({"error": "Cannot delete your own account"}), 400
            
            # 删除用户
            cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
            conn.commit()
            
            return jsonify({"message": "User deleted successfully"}), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        conn.close()

# 配置dashscope API密钥
dashscope.api_key = os.getenv('DASHSCOPE_API_KEY', 'sk-0fcfde37071d44058afad63fa810c5e3')

# 音频识别接口
@app.route('/api/audio/recognize', methods=['POST'])
@jwt_required()
def audio_recognize():
    try:
        data = request.get_json()
        if not data or 'messages' not in data:
            return jsonify({'error': '无效的请求数据'}), 400

        messages = data['messages']
        
        # 检查并处理base64音频数据
        for message in messages:
            if message['role'] == 'user':
                for content in message['content']:
                    if 'audio' in content:
                        # 从data URI中提取base64数据
                        audio_data = content['audio'].split(',')[1] if ',' in content['audio'] else content['audio']
                        # 将base64数据保存为临时文件
                        temp_audio = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
                        try:
                            temp_audio.write(base64.b64decode(audio_data))
                            temp_audio.close()
                            # 更新消息中的音频路径
                            content['audio'] = temp_audio.name
                        except Exception as e:
                            if os.path.exists(temp_audio.name):
                                os.unlink(temp_audio.name)
                            raise Exception(f'音频数据处理失败: {str(e)}')

        try:
            # 调用dashscope API
            response = MultiModalConversation.call(
                model=data.get('model', 'qwen-audio-turbo-latest'),
                messages=messages
            )
            
            # 清理临时文件
            for message in messages:
                if message['role'] == 'user':
                    for content in message['content']:
                        if 'audio' in content and os.path.exists(content['audio']):
                            os.unlink(content['audio'])
            
            # 检查响应
            if response and 'output' in response and 'choices' in response['output']:
                return jsonify(response)
            else:
                return jsonify({'error': '无效的API响应'}), 500
                
        except Exception as e:
            # 确保清理临时文件
            for message in messages:
                if message['role'] == 'user':
                    for content in message['content']:
                        if 'audio' in content and os.path.exists(content['audio']):
                            os.unlink(content['audio'])
            raise e

    except Exception as e:
        print(f'音频识别失败: {str(e)}')
        return jsonify({'error': f'音频识别失败: {str(e)}'}), 500

# 会议处理相关接口
@app.route('/api/meeting/process', methods=['POST'])
@jwt_required()
def process_meeting():
    try:
        current_user = get_jwt_identity()
        
        # 获取用户ID
        conn = get_db_connection()
        try:
            with conn.cursor() as cursor:
                cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
                user = cursor.fetchone()
                if not user:
                    return jsonify({"error": "用户不存在"}), 404
                user_id = user["id"]
        finally:
            conn.close()

        # 会议分析提示词模板
        meeting_prompt = """你是一个专业的会议助手。请帮助：
1. 提取并整理会议的主要内容
2. 识别关键讨论点
3. 列出所有决策事项
4. 提取具体的行动项目（包括负责人和截止日期）
5. 生成简洁的会议总结

请按以下格式输出：
# 会议主要内容
[主要内容概述]

# 关键讨论点
- 讨论点1
- 讨论点2
...

# 决策事项
- 决策1
- 决策2
...

# 行动项目
- [ ] 项目1 (负责人: xxx, 截止日期: xxx)
- [ ] 项目2 (负责人: xxx, 截止日期: xxx)
...

# 会议总结
[简要总结]"""

        if request.is_json:
            # 处理JSON请求（用于文本输入和音频处理）
            data = request.get_json()
            content_type = data.get('content_type', 'text')
            print(f"收到会议处理请求: {mask_base64_fields(data)}")
            
            if content_type == 'audio':
                if 'message' not in data:
                    return jsonify({'error': '缺少 message 字段（音频识别需要 message）'}), 400
                messages = [
                    {
                        "role": "system",
                        "content": [{"text": meeting_prompt}]
                    },
                    data.get('message', {})
                ]
                response = MultiModalConversation.call(
                    model='qwen-audio-turbo-latest',
                    messages=messages
                )
                if response and 'output' in response and 'choices' in response['output']:
                    return jsonify(response)
                else:
                    return jsonify({'error': '无效的API响应'}), 500
            elif content_type == 'text':
                if 'content' not in data:
                    return jsonify({'error': '缺少 content 字段（文本分析需要 content）'}), 400
                content = data.get('content', '')
                messages = [
                    {
                        "role": "system",
                        "content": [{"text": meeting_prompt}]
                    },
                    {
                        "role": "user",
                        "content": [{"text": f"这是一段会议内容，请帮我分析：\n\n{content}"}]
                    }
                ]
                # 千问模型走流式
                if data.get('model', '').startswith('qwen'):
                    def generate():
                        try:
                            response = openai_client.chat.completions.create(
                                model="qwen-omni-turbo",
                                messages=messages,
                                stream=True
                            )
                            for chunk in response:
                                if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
                                    yield f"data: {json.dumps({'chunk': chunk.choices[0].delta.content})}\n\n"
                            yield f"data: {json.dumps({'done': True})}\n\n"
                        except Exception as e:
                            print(f"流式响应错误: {str(e)}")
                            yield f"data: {json.dumps({'error': str(e)})}\n\n"
                    return Response(generate(), mimetype='text/event-stream')
                else:
                    # 其它模型直接返回普通结果
                    llm = get_llm(data.get('model', 'douban'))
                    prompt = f"{meeting_prompt}\n\n这是一段会议内容，请帮我分析：\n\n{content}"
                    try:
                        result = llm.invoke(prompt)
                        return jsonify({"response": str(result)})
                    except Exception as e:
                        return jsonify({"error": f"会议纪要生成失败: {str(e)}"}), 500
        
        else:
            # 处理文件上传请求（会议文档上传，和知识库完全一致）
            if 'file' not in request.files:
                return jsonify({"error": "未提供文件"}), 400
            file = request.files['file']
            if file.filename == '':
                return jsonify({"error": "未选择文件"}), 400
            allowed_extensions = {'pdf', 'txt', 'docx'}
            file_extension = file.filename.split('.')[-1].lower() if '.' in file.filename else ''
            if file_extension not in allowed_extensions:
                return jsonify({"error": "仅支持 PDF、TXT 和 DOCX 文件"}), 400
            file_content = ""
            try:
                # ...文件解析、分块、向量化逻辑...
                # ...如前所述...
                # ...
                try:
                    # ...向量存储和流式生成逻辑...
                    return Response(generate(), mimetype='text/event-stream')
                except Exception as e:
                    print(f"向量存储创建失败: {e}")
                    return jsonify({"error": f"向量存储创建失败: {str(e)}"}), 500
            except Exception as e:
                print(f"文件处理错误: {e}")
                return jsonify({"error": f"处理文件时出错: {str(e)}"}), 500
            # 补充兜底return，理论上不会走到，但防止遗漏
            return jsonify({"error": "未知错误，未能生成会议纪要"}), 500

    except Exception as e:
        print(f'会议处理失败: {str(e)}')
        return jsonify({'error': f'会议处理失败: {str(e)}'}), 500

@app.route('/api/meeting/upload', methods=['POST'])
@jwt_required()
def upload_meeting_file():
    current_user = get_jwt_identity()
    
    if 'file' not in request.files:
        return jsonify({"error": "未提供文件"}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({"error": "未选择文件"}), 400
    
    # 检查文件类型
    allowed_extensions = {'pdf', 'txt', 'docx'}
    file_extension = file.filename.split('.')[-1].lower() if '.' in file.filename else ''
    
    if file_extension not in allowed_extensions:
        return jsonify({"error": "仅支持 PDF、TXT 和 DOCX 文件"}), 400
    
    # Get user ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "用户不存在"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    # 处理文件内容，根据文件类型采用不同的处理方式
    file_content = ""
    
    try:
        # PDF文件处理
        if file_extension == 'pdf':
            import PyPDF2
            from io import BytesIO
            
            try:
                pdf_reader = PyPDF2.PdfReader(BytesIO(file.read()))
                for page_num in range(len(pdf_reader.pages)):
                    page = pdf_reader.pages[page_num]
                    file_content += page.extract_text() + "\n\n"
            except Exception as e:
                print(f"PDF处理错误: {e}")
                # 如果PyPDF2失败，尝试使用pdfplumber
                try:
                    import pdfplumber
                    with pdfplumber.open(BytesIO(file.stream.read())) as pdf:
                        for page in pdf.pages:
                            text = page.extract_text()
                            if text:
                                file_content += text + "\n\n"
                except Exception as e2:
                    print(f"pdfplumber处理错误: {e2}")
                    return jsonify({"error": f"无法处理PDF文件: {str(e2)}"}), 500
        
        # Word文档处理
        elif file_extension == 'docx':
            try:
                import docx
                from io import BytesIO
                
                doc = docx.Document(BytesIO(file.read()))
                for para in doc.paragraphs:
                    file_content += para.text + "\n"
            except Exception as e:
                print(f"Word文档处理错误: {e}")
                return jsonify({"error": f"无法处理Word文档: {str(e)}"}), 500
        
        # 文本文件处理
        elif file_extension == 'txt':
            try:
                # 尝试不同的编码方式
                encodings = ['utf-8', 'latin-1', 'gbk', 'gb2312', 'gb18030', 'big5']
                for encoding in encodings:
                    try:
                        file.stream.seek(0)  # 重置文件指针
                        file_content = file.read().decode(encoding)
                        break
                    except UnicodeDecodeError:
                        continue
                
                if not file_content:
                    return jsonify({"error": "无法解码文本文件，请检查文件编码"}), 500
            except Exception as e:
                print(f"文本文件处理错误: {e}")
                return jsonify({"error": f"处理文本文件时出错: {str(e)}"}), 500
        
        # 检查是否成功提取到内容
        if not file_content or len(file_content.strip()) < 10:
            return jsonify({"error": "文件内容为空或无法提取文本"}), 400
        
        # 生成唯一的会议ID
        meeting_id = f"meeting_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}"
        
        # 将会议ID和用户关联存储在Redis中
        user_meeting_key = f"meeting:{user_id}:{meeting_id}"
        redis_client.set(user_meeting_key, file_content)
        redis_client.expire(user_meeting_key, 60 * 60 * 24)  # 24小时过期
        
        # 返回会议ID和文件内容
        return jsonify({
            "meeting_id": meeting_id,
            "content": file_content
        })
        
    except Exception as e:
        print(f"会议文件处理错误: {e}")
        import traceback
        traceback.print_exc()  # 打印完整堆栈信息
        return jsonify({"error": f"会议文件处理失败: {str(e)}"}), 500

@app.route('/api/meeting/qa', methods=['POST'])
@jwt_required()
def meeting_qa():
    data = request.get_json()
    meeting_id = data.get('meeting_id')
    question = data.get('question')
    history = data.get('history', [])
    
    if not meeting_id or not question:
        return jsonify({"error": "缺少 meeting_id 或 question"}), 400
    
    current_user = get_jwt_identity()
    
    # 获取用户ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "用户不存在"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    # 从Redis获取会议内容
    user_meeting_key = f"meeting:{user_id}:{meeting_id}"
    meeting_content = redis_client.get(user_meeting_key)
    
    if not meeting_content:
        return jsonify({"error": "会议内容未找到或已过期"}), 404
    
    try:
        # 将bytes转换为字符串
        if isinstance(meeting_content, bytes):
            meeting_content = meeting_content.decode('utf-8')
        
        # 构造 prompt，带历史
        prompt = "你是一个专业的会议助手。请帮助分析会议内容并回答问题。\n\n"
        prompt += f"会议内容：\n{meeting_content}\n\n"
        
        if history:
            prompt += "历史对话：\n"
            for turn in history:
                prompt += f"用户: {turn['user']}\n助手: {turn['assistant']}\n"
        
        prompt += f"用户问题：{question}\n\n请结合会议内容和历史对话，专业、准确地回答用户问题。"
        
        # 使用LLM生成回答
        llm = get_llm()
        answer = llm.invoke(prompt)
        
        # 确保answer是字符串
        if hasattr(answer, 'content'):
            answer_text = answer.content
        else:
            answer_text = str(answer)
        
        # 更新历史
        updated_history = history + [{"user": question, "assistant": answer_text}]
        
        return jsonify({
            "answer": answer_text,
            "history": updated_history
        })
    except Exception as e:
        print(f"会议问答错误: {e}")
        import traceback
        traceback.print_exc()
        return jsonify({"error": f"会议问答失败: {str(e)}"}), 500

def mask_base64_fields(obj):
    """递归替换所有 audio/image/base64 字段为占位符"""
    if isinstance(obj, dict):
        return {k: ('<base64 omitted>' if (k in ['audio', 'image', 'base64'] and isinstance(v, str) and len(v) > 100) else mask_base64_fields(v))
                for k, v in obj.items()}
    elif isinstance(obj, list):
        return [mask_base64_fields(i) for i in obj]
    else:
        return obj

@app.route('/api/admin/users/<int:user_id>/password', methods=['PUT'])
@jwt_required()
def update_user_password(user_id):
    # 验证当前用户是否为管理员
    current_user = get_jwt_identity()
    print(f"Attempting to update password for user {user_id} by admin {current_user}")
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT role FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            print(f"Admin check result: {user}")
            if not user or user['role'] != 'admin':
                print(f"Permission denied: user={user}")
                return jsonify({"error": "权限不足"}), 403

            # 获取新密码
            data = request.get_json()
            new_password = data.get('password')
            print(f"New password received: {new_password is not None}")
            if not new_password:
                print("Password is empty")
                return jsonify({"error": "新密码不能为空"}), 400

            # 检查用户是否存在
            cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
            target_user = cursor.fetchone()
            print(f"Target user check result: {target_user}")
            if not target_user:
                print(f"User {user_id} not found")
                return jsonify({"error": "用户不存在"}), 404

            # 更新用户密码
            print(f"Executing password update for user {user_id}")
            cursor.execute(
                "UPDATE users SET password = %s WHERE id = %s",
                (new_password, user_id)
            )
            print("Password update query executed")
            conn.commit()
            print("Transaction committed")

            return jsonify({"message": "密码更新成功"})
    except Exception as e:
        print(f"Error updating user password: {str(e)}")
        return jsonify({"error": "更新密码失败"}), 500
    finally:
        conn.close()

@app.route('/api/meeting/audio', methods=['POST'])
@jwt_required()
def meeting_audio():
    data = request.get_json()
    meeting_id = data.get('meeting_id')
    audio_base64 = data.get('audio_base64')
    
    if not audio_base64:
        return jsonify({"error": "缺少音频数据"}), 400
    
    current_user = get_jwt_identity()
    
    # 获取用户ID
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id FROM users WHERE username = %s", (current_user,))
            user = cursor.fetchone()
            if not user:
                return jsonify({"error": "用户不存在"}), 404
            user_id = user["id"]
    finally:
        conn.close()
    
    try:
        import tempfile, base64, os
        # 保存音频到临时文件
        audio_bytes = base64.b64decode(audio_base64.split(',')[1] if ',' in audio_base64 else audio_base64)
        with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_audio:
            temp_audio.write(audio_bytes)
            temp_audio_path = temp_audio.name
        
        # 使用通义千问音频模型进行音频识别
        try:
            # 准备请求数据
            messages = [
                {
                    "role": "system",
                    "content": [{"text": "你是一个专业的会议助手，负责转录会议内容。"}]
                },
                {
                    "role": "user",
                    "content": [
                        {"audio": temp_audio_path},
                        {"text": "请转录这段会议录音的内容。"}
                    ]
                }
            ]
            
            # 调用dashscope API
            response = MultiModalConversation.call(
                model='qwen-audio-turbo-latest',
                messages=messages
            )
            
            # 清理临时文件
            if os.path.exists(temp_audio_path):
                os.unlink(temp_audio_path)
            
            # 检查响应
            if response and 'output' in response and 'choices' in response['output']:
                transcript = response['output']['choices'][0]['message']['content'][0]['text']
            else:
                return jsonify({'error': '音频识别失败，未能获取有效响应'}), 500
            
        except Exception as e:
            # 确保清理临时文件
            if os.path.exists(temp_audio_path):
                os.unlink(temp_audio_path)
            print(f"音频识别失败: {str(e)}")
            return jsonify({'error': f'音频识别失败: {str(e)}'}), 500
        
        # 如果指定了会议ID，则将转录内容添加到现有会议中
        if meeting_id:
            user_meeting_key = f"meeting:{user_id}:{meeting_id}"
            existing_content = redis_client.get(user_meeting_key)
            
            if existing_content:
                # 将bytes转换为字符串
                if isinstance(existing_content, bytes):
                    existing_content = existing_content.decode('utf-8')
                
                # 添加新的音频转录内容
                updated_content = existing_content + "\n\n[音频转录内容]\n" + transcript
                
                # 更新Redis中的会议内容
                redis_client.set(user_meeting_key, updated_content)
                redis_client.expire(user_meeting_key, 60 * 60 * 24)  # 24小时过期
            else:
                # 如果找不到现有会议，创建新的
                redis_client.set(user_meeting_key, f"[音频转录内容]\n{transcript}")
                redis_client.expire(user_meeting_key, 60 * 60 * 24)  # 24小时过期
        else:
            # 如果没有指定会议ID，创建新的会议
            new_meeting_id = f"meeting_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}"
            user_meeting_key = f"meeting:{user_id}:{new_meeting_id}"
            
            # 存储音频转录内容
            redis_client.set(user_meeting_key, f"[音频转录内容]\n{transcript}")
            redis_client.expire(user_meeting_key, 60 * 60 * 24)  # 24小时过期
            
            meeting_id = new_meeting_id
        
        return jsonify({
            "transcript": transcript, 
            "meeting_id": meeting_id
        })
        
    except Exception as e:
        print(f"会议音频处理错误: {e}")
        import traceback
        traceback.print_exc()
        return jsonify({"error": f"会议音频处理失败: {str(e)}"}), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000) 